home *** CD-ROM | disk | FTP | other *** search
Text File | 1990-04-30 | 14.2 KB | 375 lines | [TEXT/MPS ] |
- ; ====================================================================================
- ;
- ; Program: FracApp 2.0
- ; File: GoFigger.a
- ;
- ; by Keith Rollin & Bo3b Johnson
- ; of Apple Macintosh Developer Technical Support
- ;
- ; Copyright © 1988-1990 Apple Computer, Inc.
- ; All rights reserved.
- ;
- ; ====================================================================================
- ;
- ; Assembly support routines for FracApp 2.0
- ;
- ; InsTimeNoDrift - Installs a Time Manager Task, using Extended Time Manager if
- ; present
- ; TimeCounterThatFetchesItsOwnTaskPtr - Task that gets called on Systems earlier
- ; than 6.0.2
- ; TimeCounter - Task that gets called on System 6.0.2 or later
- ; InitCounter - Called to initialize the TimeCounterThatFetchesItsOwnTaskPtr with
- ; a reference to our global variables.
- ; GoFigger - Given a point, a range, and a dwell threshhold, returns the number of
- ; iterations to go beyond the range.
- ;
- ; ====================================================================================
-
- MC68881
- if qNeedsMC68020 then
- MACHINE MC68020
- elseif qNeedsMC68030 then
- MACHINE MC68030
- endif
-
- INCLUDE 'Traps.a'
-
-
- ; ====================================================================================
- ;
- ; PROCEDURE InsTimeNoDrift(taskRecPtr:TMTaskPtr);
- ;
- ; Install a Time Manager task. This is a copy of the glue that comes with MPW for
- ; performing such a task, except that we use Trap $A458 instead of $A058. This gets
- ; us to the same place in ROM, but the fact that bit 9 is set means something to
- ; the extended Time Manager in System 7.0; it is harmless for earlier systems. See
- ; Inside mac, page I-89 to see the format of the trap word and what bit 9 means for
- ; Operating System Traps.
- ;
- ; So what does setting bit 9 mean? On systems that support it, it means "no-drift"
- ; mode. This means that the Time Manager accounts for the overhead involved with
- ; managing tasks, and the time it takes for your task procedure to execute. Your
- ; task will be called with the frequency that you specified, rather than with an
- ; interval of <specified period> + <Time Manager Overhead> + <Task execution time>.
- ; Basically, what I mean is that it's more accurate.
- ;
- ; ====================================================================================
-
- _InsXTimeTrap OPWORD $A458
-
- SEG 'AInit'
- INSTIMENODRIFT PROC EXPORT
-
- StackFrame RECORD {RetAddr},DECR ; build a stack frame record
- taskRecPtr DS.L 1 ; pointer task record we are using
- ParamSize EQU StackFrame-* ; size of all the passed parameters
- RetAddr DS.L 1 ; place holder for return address
- ENDR
-
- WITH StackFrame
-
- MOVEA.L taskRecPtr(A7),A0 ; get the record pointer
- _InsXTimeTrap ; Install the task
-
- if qNeedsMC68020 | qNeedsMC68030 then
-
- RTD #ParamSize
-
- else
-
- MOVEA.L (A7)+,A0 ; pop the return address
- ADD #ParamSize,A7 ; trash the parameter
- JMP (A0) ; return
-
- endif
-
- ENDP
-
- ; ====================================================================================
- ;
- ; InitCounter
- ; Called when we aren't running under System 6.0.2 or later. Under those newer
- ; systems, the Time Manager calls our task procedure with A1 holding a pointer to
- ; our task record. This gives us the foothold we need to get at our global
- ; variables. For instance, just preceeding my task record, I have a variable that
- ; holds my A5 value. Once I have this, I can access any of my global variables.
- ; The problem is that we don't get this pointer in A1 on older systems. Therefore,
- ; I call InitCounter with with that pointer, and save it locally.
- ;
- ; TimeCounterThatFetchesItsOwnTaskPtr
- ; Gets the pointer to my time manager task record, and falls to TimeCounter. This
- ; routine gets installed on older systems that don't have a Time Manager that
- ; passes to us a pointer to our task record in A1. In that case, we have to
- ; remember the pointer ourselves. We can't save it in a global, as our time
- ; counter gets called at interrupt time, and there is no guarantee that A5 points
- ; to our globals. Neither is there a way to find out what A5 should be set to,
- ; so we can't just save A5, set it to the right value, do our thing, and then
- ; restore A5. The only way to remember our task record pointer is to save it
- ; locally, and retrieve it with PC relative addressing.
- ;
- ; TimeCounter
- ; Retrieves my saved A5 value, gets a reference to gCounter, and increments it.
- ; This way, gCounter is like a running stopwatch with millisecond resolution.
- ; Whenever I need to timestamp something, I just look at gCounter. It's kind of
- ; like calling the Ticks system routine, but better. Ticks aren't good enough
- ; because we would like to be able to chunk up our operations into units small
- ; enough that we don't upset the user. In other words, we want responsiveness
- ; to be very high. With this goal, we wouldn't be able to do timing with Ticks
- ; as we would like to be in and out of our calculation routines in less than
- ; 1/60 of a second. So we use a timer with a higher resolution, i.e., the
- ; Time Manager.
- ;
- ; ====================================================================================
- SEG 'ARes'
- INITCOUNTER PROC EXPORT
- IMPORT GCOUNTER:Data
- EXPORT TIMECOUNTERTHATFETCHESITSOWNTASKPTR
- EXPORT TIMECOUNTER
-
- TimeRecord RECORD {qHeader}
- myA5 DS.L 1 ; holds our A5 for our globals
- qHeader DS.B 6 ; standard OS queue header
- tmAddr DS.L 1 ; service routine [pointer]
- tmCount DS.L 1 ; timeout count [long]
- tmQSize EQU *
- ENDR
-
- WITH TimeRecord
-
- MOVE.L (SP)+,A0 ; get return address, save in register A0
- LEA tmTaskPtr,A1 ; get a pointer to local storage
- MOVE.L (SP)+,(A1) ; put pointer to task record into local storage
- JMP (A0) ; return to caller
-
- TIMECOUNTERTHATFETCHESITSOWNTASKPTR
- MOVE.L tmTaskPtr(PC),A1 ; retrieve the pointer to task record
- TIMECOUNTER
- MOVE.L myA5(A1),A0 ; get our A5 value
- ADDQ.L #1,GCOUNTER(A0) ; increment our global gCounter
- MOVE.L A1,A0 ; put task pointer in A0 for PrimeTime
- MOVEQ.L #1,D0 ; wake up time or 1 millisecond
- _PrimeTime ; re-install ourselves
- RTS
-
- tmTaskPtr DC.L 0 ; holds pointer to time task record on
- ; old systems that won't pass it to us
- ; when they call us.
-
- ENDP
-
-
- ; ====================================================================================
- ;
- ; PROCEDURE GoFigger(x, y, Po, Qo: Extended; M, K: Integer; VAR kol: Integer);
- ;
- ; This is the heart and soul of this program. Given a point and some other
- ; defining constants, this routine figures out what color the point should be.
- ; It works like this:
- ;
- ; Mandelbrot fractals are calculated on the complex coordinate plane. This means
- ; that complex numbers of the form a + ib are plotted in x,y fashion on a two
- ; dimensional grid. The value 'a' is plotted in the x, or real, direction, and
- ; the value 'b' is plotted in the y, or imaginary, direction.
- ;
- ; Given a point a + ib, we square it and add a complex constant C = Po + iQo. We
- ; then check to see how far the result is away from the first point. If it is
- ; farther than some limit (called M here), we are done. If the result is within
- ; that limit, we apply the formula again to that result. We keep this process up
- ; until we either go outside the limit "M", or we have performed this process a
- ; certain number of times (called the dwell limit). It is this number of
- ; iterations that determines the color of the pixel. If we exceed our maximum
- ; number of iterations, we map the color to black.
- ;
- ; ====================================================================================
-
- SEG 'ARes'
- TFRACAPPENGINE_GOFIGGER PROC EXPORT
-
- StackFrame RECORD {A6Link},DECR
- xPtr DS.L 1 ; ptr to initial 'a'
- yPtr DS.L 1 ; ptr to initial 'b'
- PoPtr DS.L 1 ; ptr to real part of constant
- QoPtr DS.L 1 ; ptr to imag part of constant
- M DS.W 1 ; maximum distance to roam
- K DS.W 1 ; dwell limit
- kolPtr DS.L 1 ; location to return iteration count
- SELF DS.L 1 ; reference to myself (my object)
- ParamSize EQU StackFrame-* ; size of all the passed parameters
- RetAddr DS.L 1 ; place holder for return address
- A6Link DS.L 1
- ENDR
-
- x EQU FP7 ; hold in register for speed
- y EQU FP6 ; hold in register for speed
- Po EQU FP5 ; hold in register for speed
- Qo EQU FP4 ; hold in register for speed
- xSquared EQU FP3 ;
- ySquared EQU FP2
- radius EQU FP1 ; holds 'M' for speed
- scratch EQU FP0
-
- dwellMax EQU D1 ; holds 'K' for speed
- countDown EQU D2 ; holds number of iterations
-
- savedRegs REG D1-D2
- savedFPRegs FREG FP4-FP7
-
- WITH StackFrame
-
- LINK A6,#A6Link ; Link so that I can get to my VARs
- MOVEM.L savedRegs,-(A7) ; Save the registers I'll trash that...
- FMOVEM savedFPRegs,-(A7) ; ...aren't defined as scratch registers
-
- MOVEA.L QoPtr(A6),A0 ; Get my initial point
- FMOVE.X (A0),Qo
- MOVEA.L PoPtr(A6),A0
- FMOVE.X (A0),Po
-
- MOVEA.L yPtr(A6),A0 ; Get the origin
- FMOVE.X (A0),y
- MOVEA.L xPtr(A6),A0
- FMOVE.X (A0),x
-
- MOVE K(A6),dwellMax ; Get my dwell time
- FMOVE.W M(A6),radius ; Get the distance limit
-
- MOVE dwellMax,countDown ; set counter to maximum (we'll count down)
-
- BRA.S TestEnd
-
- ; Given a point: pt = x + iy,
- ; and a constant: C = Po + iQo,
- ; iteratively calculate pt2 = pt^2 + C
- ; = (x + iy)^2 + (Po + iQo)
- ; = (x^2 + 2ixy - y^2) + (Po + iQo)
- ; = (x^2 - y^2 + Po) + i(2xy + Qo)
- ; until pt2 is at least "M" units from the original point.
- ; The number of times that it took us to get to this state gets
- ; mapped into the color that we use for that point. If we iterated more
- ; than a certain maximum, then we cap the color to that maximum.
-
- loop
- ; Make y := 2*x*y + Qo
- FADD y,y ; y := y+y := 2*y
- FMUL x,y ; y := x*y := x*(2*y) := 2*x*y
- FADD Qo,y ; y := 2*x*y + Qo
-
- FMOVE.X xSquared,x ; x := x^2 - y^2 + Po
- FSUB ySquared,x
- FADD Po,x
-
-
- TestEnd
- FMOVE.X y,ySquared ; ySquared := y*y
- FMUL y,ySquared
- FMOVE.X x,xSquared ; xSquared := x*x
- FMUL x,xSquared
-
- FMOVE.X ySquared,scratch ; is xSquared + ySquared > radius?
- FADD xSquared,scratch
- FCMP radius,scratch
- FDBGE countDown,loop ; no (also, test the threshold counter)
-
- OutaHere
- MOVEA.L kolPtr(A6),A0 ; get a pointer to our return variable
- SUB countDown,dwellMax ; get number of iterations
- MOVE dwellMax,(A0) ; store number iterations into kol
- FMOVEM (A7)+,savedFPRegs ; restore the registers we nuked
- MOVEM.L (A7)+,savedRegs
- UNLK A6
-
- if qNeedsMC68020 | qNeedsMC68030 then
-
- RTD #ParamSize ; Nice, fast 68020 return
-
- else
-
- MOVEA.L (A7)+,A0 ; pop the return address
- ADD #ParamSize,A7 ; trash the parameter
- JMP (A0) ; return
-
- endif
-
- ENDP
- END
-
-
-
- == ==== ======== == ==== ==== ==
- = TIME ANALYSIS OF CORE LOOP =
- == ==== ======== == ==== ==== ==
-
- ;
- ; This is an analysis of the loop of GoFigger that extends from the "loop" label
- ; all the way down to the FDGBE instruction (just before the "OoutaHere" label).
- ; Using the performance tools, I've noticed that FracApp spends most of its time
- ; in this loop (as much as 80-90%). Therefore, it's very critical that this loop
- ; run as fast as possible, so that we can see our pictures as fast as possible.
- ;
- ; This routine was originally written in Pascal, and compiled with FPU code
- ; generation turned on. The first thing we did to make this faster was to recode
- ; it into Assembly. After that we did an analysis to make sure that this code was
- ; as fast as possible, taking into account the caching capabilities of the 68882.
- ; following is results of that analysis.
- ;
- ; This analysis follows the procedure outlined in section 8.5.1.3 of the Motorola
- ; MC68881/MC68882 Floating Point Coprocessor User's Manual
- ;
-
- 68881 68882 Tail Next Head Overlap := Min(Tail, NextHead)
- ----- ----- ---- --------- -------
- FADD y,y 51 56/17/35 35 17 17
- FMUL x,y 71 76/17/55 55 17 17
- FADD Qo,y 51 56/17/35 35 21+17 35
-
- FMOVE.X xSquared,x 33 21/21/00 - - -
- FSUB ySquared,x 51 56/17/35 35 17 17
- FADD Po,x 51 56/17/35 35 21+17 35
-
- FMOVE.X y,ySquared 33 21/21/00 - - -
- FMUL y,ySquared 71 76/17/55 55 21+17 38
- FMOVE.X x,xSquared 33 21/21/00 - - -
- FMUL x,xSquared 71 76/17/55 55 21+17 38
-
- FMOVE.X ySquared,scratch 33 21/21/00 - - -
- FADD xSquared,scratch 51 56/17/35 35 17 17
- FCMP radius,scratch 33 38/17/17 - - -
- ---- -------- ---------------------------
- 633 630 Overlap = 214
-
- Net results:
- 68881 = 633
- 68882 = 630-214 = 416
- ratio = 633/416 = 1.52
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;
- ; The following is a little chart that should give you an idea what happens with
- ; the concurrency feature of the MC68882. The head of the first instruction is
- ; executed. When that is done, it proceeds to the tail of the first instruction,
- ; and the head of the second instruction is entered. When both the tail of the
- ; first instruction and the head of the second instruction are done, we move into
- ; the tail of the second instruction, and proceed to the head of the third
- ; instruction. Some instructions, like FMOVE, have no tails, and can execute
- ; completely before the tail of the previous instruction is done, and can proceed
- ; to the head of the instruction that comes after it. Note that all of this
- ; occurs only if there is no register conflict. See the “Motorola MC68881/MC68882
- ; Floating Point Coprocessor User's Manual” and “Macintosh Technote #236: Speedy
- ; the Math Coprocessor” for more information.
- ;
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- FADD + 17 -+---- 35 ----+
- FMUL + 17 -+ +------- 55 -------+
- FADD + 17 -+ +---- 35 ----+
- FMOVE +- 21 --+
- FSUB + 17 -+---- 35 ----+
- FADD + 17 -+ +---- 35 ----+
- FMOVE +- 21 --+
- FMUL + 17 -+------- 55 -------+
- FMOVE +- 21 --+
- FMUL + 17 -+ +------- 55 -------+
- FMOVE +- 21 --+
- FADD + 17 -+ +---- 35 ----+
- FCMP + 17 -+ + 17 -+
-